"""
知识库服务层
提供知识库文件、文件夹、分享等业务逻辑
"""
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any, Tuple
from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorDatabase
from fastapi import HTTPException, UploadFile, status
from pymongo import ASCENDING, DESCENDING
import uuid
import secrets
import logging
from pathlib import Path
import mimetypes

from app.models.knowledge import (
    KnowledgeFile,
    FileType,
    FileFormat,
    FilePermission
)
from app.models.knowledge_folder import KnowledgeFolder, FolderPermission
from app.models.knowledge_share import KnowledgeShare
from app.models.knowledge_comment import KnowledgeComment
from app.models.knowledge_favorite import KnowledgeFavorite
from app.schemas.knowledge import (
    KnowledgeFileCreate,
    KnowledgeFileUpdate,
    KnowledgeFileResponse,
    KnowledgeFolderCreate,
    KnowledgeFolderUpdate,
    KnowledgeFolderResponse,
    KnowledgeShareCreate,
    KnowledgeShareResponse,
    KnowledgeCommentCreate,
    KnowledgeCommentResponse,
    KnowledgeStats
)
from app.core.database import get_minio
from app.core.config import settings

logger = logging.getLogger(__name__)

# MinIO存储路径前缀
MINIO_KNOWLEDGE_PREFIX = "knowledge-base"

# 分享链接相关常量
MAX_SHARE_DURATION_HOURS = 24 * 30  # 最长30天
DEFAULT_SHARE_PATH_TEMPLATE = "/share/knowledge/{token}"

# 允许的文件类型和大小限制（字节）
ALLOWED_EXTENSIONS = {
    # 文档格式
    '.pdf': (FileFormat.PDF, FileType.DOCUMENT),
    '.doc': (FileFormat.DOC, FileType.DOCUMENT),
    '.docx': (FileFormat.DOCX, FileType.DOCUMENT),
    '.xls': (FileFormat.XLS, FileType.DOCUMENT),
    '.xlsx': (FileFormat.XLSX, FileType.DOCUMENT),
    '.ppt': (FileFormat.PPT, FileType.DOCUMENT),
    '.pptx': (FileFormat.PPTX, FileType.DOCUMENT),
    '.txt': (FileFormat.TXT, FileType.DOCUMENT),
    '.md': (FileFormat.MD, FileType.DOCUMENT),
    '.html': (FileFormat.HTML, FileType.DOCUMENT),
    # 图片格式
    '.jpg': (FileFormat.JPG, FileType.IMAGE),
    '.jpeg': (FileFormat.JPEG, FileType.IMAGE),
    '.png': (FileFormat.PNG, FileType.IMAGE),
    '.gif': (FileFormat.GIF, FileType.IMAGE),
    '.bmp': (FileFormat.BMP, FileType.IMAGE),
    '.webp': (FileFormat.WEBP, FileType.IMAGE),
    '.svg': (FileFormat.SVG, FileType.IMAGE),
    # 视频格式
    '.mp4': (FileFormat.MP4, FileType.VIDEO),
    '.avi': (FileFormat.AVI, FileType.VIDEO),
    '.mov': (FileFormat.MOV, FileType.VIDEO),
    '.wmv': (FileFormat.WMV, FileType.VIDEO),
    # 音频格式
    '.mp3': (FileFormat.MP3, FileType.AUDIO),
    '.wav': (FileFormat.WAV, FileType.AUDIO),
    '.ogg': (FileFormat.OGG, FileType.AUDIO),
    # 压缩格式
    '.zip': (FileFormat.ZIP, FileType.OTHER),
    '.rar': (FileFormat.RAR, FileType.OTHER),
}

MAX_FILE_SIZE = getattr(settings, "MAX_FILE_SIZE", 100 * 1024 * 1024)  # 默认100MB


class KnowledgeService:
    """知识库服务类"""
    
    def __init__(self, db: AsyncIOMotorDatabase):
        self.db = db
        self._bucket_name = settings.MINIO_BUCKET_NAME
    
    async def _get_deleted_status_code(self) -> Optional[str]:
        """获取"已删除"状态代码（从状态管理系统动态获取）"""
        from app.utils.state_utils import get_state_code_by_name_keywords
        return await get_state_code_by_name_keywords(self.db, "document", ["已删除", "删除", "deleted"])
    
    def _to_object_id(self, value: Any, field_name: str = "ID") -> Optional[ObjectId]:
        """将值转换为ObjectId"""
        if value is None:
            return None
        if isinstance(value, ObjectId):
            return value
        if isinstance(value, str) and ObjectId.is_valid(value):
            return ObjectId(value)
        return None
    
    def _validate_object_id(self, value: Any, field_name: str = "ID") -> ObjectId:
        """验证并转换ObjectId"""
        obj_id = self._to_object_id(value, field_name)
        if obj_id is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"无效的{field_name}格式"
            )
        return obj_id
    
    def _get_file_type_and_format(self, filename: str, mime_type: Optional[str] = None) -> Tuple[FileFormat, FileType]:
        """根据文件名和MIME类型确定文件格式和类型"""
        ext = Path(filename).suffix.lower()
        
        # 首先尝试通过扩展名匹配
        if ext in ALLOWED_EXTENSIONS:
            return ALLOWED_EXTENSIONS[ext]
        
        # 如果扩展名不匹配，尝试通过MIME类型推断
        if mime_type:
            mime_to_format = {
                'application/pdf': (FileFormat.PDF, FileType.DOCUMENT),
                'application/msword': (FileFormat.DOC, FileType.DOCUMENT),
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document': (FileFormat.DOCX, FileType.DOCUMENT),
                'application/vnd.ms-excel': (FileFormat.XLS, FileType.DOCUMENT),
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': (FileFormat.XLSX, FileType.DOCUMENT),
                'application/vnd.ms-powerpoint': (FileFormat.PPT, FileType.DOCUMENT),
                'application/vnd.openxmlformats-officedocument.presentationml.presentation': (FileFormat.PPTX, FileType.DOCUMENT),
                'text/plain': (FileFormat.TXT, FileType.DOCUMENT),
                'text/markdown': (FileFormat.MD, FileType.DOCUMENT),
                'text/html': (FileFormat.HTML, FileType.DOCUMENT),
                'image/jpeg': (FileFormat.JPG, FileType.IMAGE),
                'image/png': (FileFormat.PNG, FileType.IMAGE),
                'image/gif': (FileFormat.GIF, FileType.IMAGE),
                'image/webp': (FileFormat.WEBP, FileType.IMAGE),
                'image/svg+xml': (FileFormat.SVG, FileType.IMAGE),
                'video/mp4': (FileFormat.MP4, FileType.VIDEO),
                'audio/mpeg': (FileFormat.MP3, FileType.AUDIO),
                'audio/wav': (FileFormat.WAV, FileType.AUDIO),
            }
            if mime_type in mime_to_format:
                return mime_to_format[mime_type]
        
        # 默认返回其他类型
        return (FileFormat.OTHER, FileType.OTHER)
    
    def _generate_storage_path(self, file_id: str, filename: str) -> str:
        """生成MinIO存储路径"""
        now = datetime.utcnow()
        year = now.strftime("%Y")
        month = now.strftime("%m")
        ext = Path(filename).suffix.lower()
        return f"{MINIO_KNOWLEDGE_PREFIX}/files/{year}/{month}/{file_id}/original{ext}"
    
    async def upload_file(
        self,
        user: dict,
        file: UploadFile,
        file_data: KnowledgeFileCreate
    ) -> KnowledgeFileResponse:
        """上传文件到知识库"""
        # 验证文件
        if not file.filename:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件名不能为空"
            )
        
        # 读取文件内容
        content = await file.read()
        file_size = len(content)
        
        # 检查文件大小
        if file_size > MAX_FILE_SIZE:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"文件大小超过最大限制 ({MAX_FILE_SIZE / 1024 / 1024:.1f}MB)"
            )
        
        # 检查文件扩展名
        ext = Path(file.filename).suffix.lower()
        if ext not in ALLOWED_EXTENSIONS:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"不支持的文件格式: {ext}"
            )
        
        # 确定文件类型和格式
        file_format, file_type = self._get_file_type_and_format(file.filename, file.content_type)
        
        # 检查重复文档（同一文件夹下，文件名或标题相同）
        folder_obj_id = self._to_object_id(file_data.folder_id) if file_data.folder_id else None
        
        # 从状态管理系统动态获取"已删除"状态代码并过滤
        deleted_status_code = await self._get_deleted_status_code()
        
        # 构建重复检查查询条件（排除已删除的文件）
        duplicate_query: Dict[str, Any] = {}
        if deleted_status_code:
            duplicate_query["status"] = {"$ne": deleted_status_code}
        duplicate_query["$or"] = [
            {"deleted_at": None},
            {"deleted_at": {"$exists": False}}
        ]
        
        # 检查同一文件夹下是否有相同的文件名或标题
        if folder_obj_id:
            duplicate_query["folder_id"] = folder_obj_id
        else:
            # 如果没有指定文件夹，检查根目录（folder_id为None或不存在）
            if "$or" in duplicate_query:
                # 需要组合条件
                folder_condition = [
                    {"folder_id": None},
                    {"folder_id": {"$exists": False}}
                ]
                duplicate_query["$and"] = [
                    {"$or": duplicate_query.pop("$or")},
                    {"$or": folder_condition}
                ]
            else:
                duplicate_query["$or"] = [
                    {"folder_id": None},
                    {"folder_id": {"$exists": False}}
                ]
        
        # 检查文件名重复
        filename_query = duplicate_query.copy()
        filename_query["filename"] = file.filename
        filename_duplicate = await self.db.knowledge_files.find_one(filename_query)
        if filename_duplicate:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f'文件已存在：同一文件夹下已存在名为"{file.filename}"的文件'
            )
        
        # 检查标题重复
        title_query = duplicate_query.copy()
        title_query["title"] = file_data.title
        title_duplicate = await self.db.knowledge_files.find_one(title_query)
        if title_duplicate:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f'文档已存在：同一文件夹下已存在标题为"{file_data.title}"的文档'
            )
        
        # 生成文件ID和存储路径
        file_id = ObjectId()
        storage_path = self._generate_storage_path(str(file_id), file.filename)
        
        # 上传到MinIO
        try:
            minio_client = get_minio()
            from io import BytesIO
            file_stream = BytesIO(content)
            
            minio_client.put_object(
                self._bucket_name,
                storage_path,
                file_stream,
                length=file_size,
                content_type=file.content_type or mimetypes.guess_type(file.filename)[0] or "application/octet-stream"
            )
        except Exception as e:
            logger.error(f"上传文件到MinIO失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="文件上传失败"
            )
        
        # 从状态管理系统获取默认状态
        from app.utils.state_utils import get_default_state_code
        default_status = await get_default_state_code(self.db, "document")
        
        # 保存文件记录到数据库
        now = datetime.utcnow()
        file_doc = {
            "_id": file_id,
            "title": file_data.title,
            "filename": file.filename,
            "file_path": storage_path,
            "file_size": file_size,
            "mime_type": file.content_type or mimetypes.guess_type(file.filename)[0] or "application/octet-stream",
            "file_type": file_type.value,
            "format": file_format.value,
            "folder_id": self._to_object_id(file_data.folder_id) if file_data.folder_id else None,
            "tags": file_data.tags or [],
            "description": file_data.description,
            "project_id": self._to_object_id(file_data.project_id) if file_data.project_id else None,
            "author_id": ObjectId(user.get("user_id") or user.get("id")),
            "organization_id": self._to_object_id(user.get("organization_id")) if user.get("organization_id") else None,
            "is_public": file_data.is_public,
            "permission": file_data.permission.value,
            "download_count": 0,
            "view_count": 0,
            "favorite_count": 0,
            "status": default_status or "",
            "created_at": now,
            "updated_at": now,
            "deleted_at": None
        }
        
        await self.db.knowledge_files.insert_one(file_doc)
        
        # 注意：不再手动更新文件夹的文件计数
        # 文件计数会在查询文件夹列表时动态计算，确保准确性
        
        # 返回文件信息
        return await self.get_file(user, str(file_id))
    
    async def create_empty_file(
        self,
        user: dict,
        file_data: KnowledgeFileCreate,
        file_format: str = "txt"
    ) -> KnowledgeFileResponse:
        """创建空文档（文本类文件）"""
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        folder_obj_id = self._to_object_id(file_data.folder_id) if file_data.folder_id else None
        
        # 从状态管理系统动态获取"已删除"状态代码并过滤
        deleted_status_code = await self._get_deleted_status_code()
        
        # 构建重复检查查询条件（排除已删除的文件）
        duplicate_query: Dict[str, Any] = {}
        if deleted_status_code:
            duplicate_query["status"] = {"$ne": deleted_status_code}
        duplicate_query["$or"] = [
            {"deleted_at": None},
            {"deleted_at": {"$exists": False}}
        ]
        
        # 检查同一文件夹下是否有相同的标题
        if folder_obj_id:
            duplicate_query["folder_id"] = folder_obj_id
        else:
            # 如果没有指定文件夹，检查根目录（folder_id为None或不存在）
            if "$or" in duplicate_query:
                # 需要组合条件
                folder_condition = [
                    {"folder_id": None},
                    {"folder_id": {"$exists": False}}
                ]
                duplicate_query["$and"] = [
                    {"$or": duplicate_query.pop("$or")},
                    {"$or": folder_condition}
                ]
            else:
                duplicate_query["$or"] = [
                    {"folder_id": None},
                    {"folder_id": {"$exists": False}}
                ]
        
        # 检查标题重复
        title_query = duplicate_query.copy()
        title_query["title"] = file_data.title
        title_duplicate = await self.db.knowledge_files.find_one(title_query)
        if title_duplicate:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f'文档已存在：同一文件夹下已存在标题为"{file_data.title}"的文档'
            )
        
        # 确定文件格式和类型
        format_map = {
            "txt": (FileFormat.TXT, FileType.DOCUMENT),
            "md": (FileFormat.MD, FileType.DOCUMENT),
            "html": (FileFormat.HTML, FileType.DOCUMENT),
            "json": (FileFormat.OTHER, FileType.DOCUMENT),
            "xml": (FileFormat.OTHER, FileType.DOCUMENT),
            "yaml": (FileFormat.OTHER, FileType.DOCUMENT),
            "yml": (FileFormat.OTHER, FileType.DOCUMENT),
            "csv": (FileFormat.OTHER, FileType.DOCUMENT)
        }
        file_format_enum, file_type = format_map.get(file_format.lower(), (FileFormat.TXT, FileType.DOCUMENT))
        
        # 生成文件名
        filename = f"{file_data.title}.{file_format.lower()}"
        
        # 生成文件ID和存储路径
        file_id = ObjectId()
        storage_path = self._generate_storage_path(str(file_id), filename)
        
        # 创建空文件内容
        content = b""
        content_size = 0
        
        # 上传到MinIO
        try:
            minio_client = get_minio()
            from io import BytesIO
            file_stream = BytesIO(content)
            
            mime_type_map = {
                "txt": "text/plain",
                "md": "text/markdown",
                "html": "text/html",
                "json": "application/json",
                "xml": "application/xml",
                "yaml": "text/yaml",
                "yml": "text/yaml",
                "csv": "text/csv"
            }
            mime_type = mime_type_map.get(file_format.lower(), "text/plain")
            
            minio_client.put_object(
                self._bucket_name,
                storage_path,
                file_stream,
                length=content_size,
                content_type=mime_type
            )
        except Exception as e:
            logger.error(f"创建空文件到MinIO失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="创建文档失败"
            )
        
        # 从状态管理系统获取默认状态
        from app.utils.state_utils import get_default_state_code
        default_status = await get_default_state_code(self.db, "document")
        
        # 保存文件记录到数据库
        now = datetime.utcnow()
        file_doc = {
            "_id": file_id,
            "title": file_data.title,
            "filename": filename,
            "file_path": storage_path,
            "file_size": content_size,
            "mime_type": mime_type,
            "file_type": file_type.value,
            "format": file_format_enum.value,
            "folder_id": folder_obj_id,
            "tags": file_data.tags or [],
            "description": file_data.description,
            "project_id": self._to_object_id(file_data.project_id) if file_data.project_id else None,
            "author_id": user_id,
            "organization_id": self._to_object_id(user.get("organization_id")) if user.get("organization_id") else None,
            "is_public": file_data.is_public,
            "permission": file_data.permission.value,
            "download_count": 0,
            "view_count": 0,
            "favorite_count": 0,
            "status": default_status or "",
            "created_at": now,
            "updated_at": now,
            "deleted_at": None
        }
        
        await self.db.knowledge_files.insert_one(file_doc)
        
        # 如果指定了文件夹，更新文件夹的文件计数
        if folder_obj_id:
            await self.db.knowledge_folders.update_one(
                {"_id": folder_obj_id},
                {"$inc": {"file_count": 1}}
            )
        
        # 返回文件信息
        return await self.get_file(user, str(file_id))
    
    async def get_file(self, user: dict, file_id: str) -> KnowledgeFileResponse:
        """获取文件详情"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        
        # 查询文件
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查文件是否已删除（从状态管理系统动态获取"已删除"状态代码）
        deleted_status_code = await self._get_deleted_status_code()
        # 检查status是否为已删除状态
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查deleted_at字段（无论是否有已删除状态码，都要检查deleted_at）
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 权限检查（简化版，后续可以完善）
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        if file_doc.get("permission") == FilePermission.PRIVATE.value:
            if file_doc.get("author_id") != user_id:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="无权访问此文件"
                )
        
        # 获取关联信息
        folder_name = None
        if file_doc.get("folder_id"):
            folder = await self.db.knowledge_folders.find_one({"_id": file_doc["folder_id"]})
            if folder:
                folder_name = folder.get("name")
        
        project_name = None
        if file_doc.get("project_id"):
            project = await self.db.projects.find_one({"_id": file_doc["project_id"]})
            if project:
                project_name = project.get("name")
        
        author_name = None
        author_avatar = None
        if file_doc.get("author_id"):
            author = await self.db.users.find_one({"_id": file_doc["author_id"]})
            if author:
                author_name = author.get("name") or author.get("username")
                author_avatar = author.get("avatar")
        
        # 检查是否已收藏
        is_favorited = await self.db.knowledge_favorites.find_one({
            "file_id": file_obj_id,
            "user_id": user_id
        }) is not None
        
        return KnowledgeFileResponse(
            id=str(file_doc["_id"]),
            title=file_doc.get("title", ""),
            filename=file_doc.get("filename", ""),
            file_path=file_doc.get("file_path", ""),
            file_size=file_doc.get("file_size", 0),
            mime_type=file_doc.get("mime_type", ""),
            file_type=file_doc.get("file_type", ""),
            format=file_doc.get("format", ""),
            folder_id=str(file_doc["folder_id"]) if file_doc.get("folder_id") else None,
            folder_name=folder_name,
            tags=file_doc.get("tags", []),
            description=file_doc.get("description"),
            project_id=str(file_doc["project_id"]) if file_doc.get("project_id") else None,
            project_name=project_name,
            author_id=str(file_doc["author_id"]),
            author_name=author_name,
            author_avatar=author_avatar,
            organization_id=str(file_doc["organization_id"]) if file_doc.get("organization_id") else None,
            is_public=file_doc.get("is_public", False),
            permission=file_doc.get("permission", FilePermission.PRIVATE.value),
            download_count=file_doc.get("download_count", 0),
            view_count=file_doc.get("view_count", 0),
            favorite_count=file_doc.get("favorite_count", 0),
            status=file_doc.get("status", ""),
            created_at=file_doc.get("created_at", datetime.utcnow()),
            updated_at=file_doc.get("updated_at", datetime.utcnow()),
            is_favorited=is_favorited
        )
    
    async def _get_folder_and_children_ids(self, folder_id: ObjectId) -> List[ObjectId]:
        """
        递归获取文件夹及其所有子文件夹的ID列表
        返回：[folder_id, child1_id, child2_id, ...]
        """
        folder_ids = [folder_id]
        
        # 递归查找所有子文件夹
        async def get_children_ids(parent_id: ObjectId) -> None:
            cursor = self.db.knowledge_folders.find({"parent_id": parent_id})
            async for child_folder in cursor:
                child_id = child_folder["_id"]
                folder_ids.append(child_id)
                # 递归查找子文件夹的子文件夹
                await get_children_ids(child_id)
        
        await get_children_ids(folder_id)
        return folder_ids
    
    async def get_files(
        self,
        user: dict,
        page: int = 1,
        size: int = 20,
        search: Optional[str] = None,
        file_type: Optional[str] = None,
        format: Optional[str] = None,
        project_id: Optional[str] = None,
        folder_id: Optional[str] = None,
        tags: Optional[List[str]] = None,
        sort_by: str = "created_at",
        sort_order: str = "desc"
    ) -> Tuple[List[KnowledgeFileResponse], int]:
        """获取文件列表"""
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 从状态管理系统动态获取"已删除"状态代码并过滤
        deleted_status_code = await self._get_deleted_status_code()
        
        # 构建查询条件（排除已删除的文件）
        # 必须同时满足：1. 未删除（deleted_at为None或不存在），2. status不等于已删除状态（如果有）
        and_conditions = []
        
        # 1. 过滤deleted_at字段（无论是否有已删除状态码，都要过滤deleted_at）
        and_conditions.append({
            "$or": [
                {"deleted_at": None},
                {"deleted_at": {"$exists": False}}
            ]
        })
        
        # 2. 过滤已删除状态（如果有已删除状态码）
        if deleted_status_code:
            and_conditions.append({"status": {"$ne": deleted_status_code}})
        
        # 搜索条件
        search_conditions = []
        if search:
            search_conditions = [
                {"title": {"$regex": search, "$options": "i"}},
                {"filename": {"$regex": search, "$options": "i"}},
                {"description": {"$regex": search, "$options": "i"}}
            ]
        
        # 文件类型筛选
        if file_type:
            and_conditions.append({"file_type": file_type})
        
        # 文件格式筛选
        if format:
            and_conditions.append({"format": format})
        
        # 项目筛选
        if project_id:
            and_conditions.append({"project_id": self._validate_object_id(project_id, "项目ID")})
        
        # 文件夹筛选（包括所有子文件夹）
        if folder_id:
            if folder_id == "__no_folder__":
                and_conditions.append({"folder_id": None})
            else:
                folder_obj_id = self._validate_object_id(folder_id, "文件夹ID")
                # 递归获取文件夹及其所有子文件夹的ID列表
                folder_ids = await self._get_folder_and_children_ids(folder_obj_id)
                # 使用 $in 查询文件夹及其所有子文件夹中的文件
                and_conditions.append({"folder_id": {"$in": folder_ids}})
        
        # 标签筛选
        if tags:
            and_conditions.append({"tags": {"$in": tags}})
        
        # 权限筛选（简化版）
        # 只显示公开的、团队的或用户自己创建的文件
        permission_conditions = [
            {"is_public": True},
            {"permission": FilePermission.PUBLIC.value},
            {"permission": FilePermission.TEAM.value},
            {"author_id": user_id}
        ]
        
        # 组合搜索条件和权限条件
        if search_conditions:
            # 如果有搜索条件，需要同时满足搜索和权限条件
            and_conditions.append({
                "$and": [
                    {"$or": search_conditions},
                    {"$or": permission_conditions}
                ]
            })
        else:
            # 如果没有搜索条件，只需要权限条件
            and_conditions.append({"$or": permission_conditions})
        
        # 组合所有条件
        query = {"$and": and_conditions} if len(and_conditions) > 0 else {}
        
        # 排序
        sort_direction = DESCENDING if sort_order == "desc" else ASCENDING
        sort_field = sort_by if sort_by in ["created_at", "updated_at", "title", "file_size", "view_count", "download_count"] else "created_at"
        
        # 计算总数
        total = await self.db.knowledge_files.count_documents(query)
        
        # 分页查询
        skip = (page - 1) * size
        cursor = self.db.knowledge_files.find(query).sort(sort_field, sort_direction).skip(skip).limit(size)
        
        files = []
        async for file_doc in cursor:
            # 获取关联信息
            folder_name = None
            if file_doc.get("folder_id"):
                folder = await self.db.knowledge_folders.find_one({"_id": file_doc["folder_id"]})
                if folder:
                    folder_name = folder.get("name")
            
            project_name = None
            if file_doc.get("project_id"):
                project = await self.db.projects.find_one({"_id": file_doc["project_id"]})
                if project:
                    project_name = project.get("name")
            
            author_name = None
            author_avatar = None
            if file_doc.get("author_id"):
                author = await self.db.users.find_one({"_id": file_doc["author_id"]})
                if author:
                    author_name = author.get("name") or author.get("username")
                    author_avatar = author.get("avatar")
            
            # 检查是否已收藏
            is_favorited = await self.db.knowledge_favorites.find_one({
                "file_id": file_doc["_id"],
                "user_id": user_id
            }) is not None
            
            files.append(KnowledgeFileResponse(
                id=str(file_doc["_id"]),
                title=file_doc.get("title", ""),
                filename=file_doc.get("filename", ""),
                file_path=file_doc.get("file_path", ""),
                file_size=file_doc.get("file_size", 0),
                mime_type=file_doc.get("mime_type", ""),
                file_type=file_doc.get("file_type", ""),
                format=file_doc.get("format", ""),
                folder_id=str(file_doc["folder_id"]) if file_doc.get("folder_id") else None,
                folder_name=folder_name,
                tags=file_doc.get("tags", []),
                description=file_doc.get("description"),
                project_id=str(file_doc["project_id"]) if file_doc.get("project_id") else None,
                project_name=project_name,
                author_id=str(file_doc["author_id"]),
                author_name=author_name,
                author_avatar=author_avatar,
                organization_id=str(file_doc["organization_id"]) if file_doc.get("organization_id") else None,
                is_public=file_doc.get("is_public", False),
                permission=file_doc.get("permission", FilePermission.PRIVATE.value),
                download_count=file_doc.get("download_count", 0),
                view_count=file_doc.get("view_count", 0),
                favorite_count=file_doc.get("favorite_count", 0),
                status=file_doc.get("status", ""),
                created_at=file_doc.get("created_at", datetime.utcnow()),
                updated_at=file_doc.get("updated_at", datetime.utcnow()),
                is_favorited=is_favorited
            ))
        
        return files, total
    
    async def update_file(
        self,
        user: dict,
        file_id: str,
        file_data: KnowledgeFileUpdate
    ) -> KnowledgeFileResponse:
        """更新文件信息"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限（只有创建者可以修改）
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权修改此文件"
            )
        
        # 如果更新标题，检查重复
        if file_data.title is not None and file_data.title != file_doc.get("title"):
            folder_obj_id = file_doc.get("folder_id")
            if file_data.folder_id is not None:
                folder_obj_id = self._to_object_id(file_data.folder_id) if file_data.folder_id else None
            
            # 从状态管理系统动态获取"已删除"状态代码并过滤
            deleted_status_code = await self._get_deleted_status_code()
            
            # 构建重复检查查询条件（排除已删除的文件和当前文件）
            duplicate_query: Dict[str, Any] = {
                "_id": {"$ne": file_obj_id}  # 排除当前文件
            }
            if deleted_status_code:
                duplicate_query["status"] = {"$ne": deleted_status_code}
            duplicate_query["$or"] = [
                {"deleted_at": None},
                {"deleted_at": {"$exists": False}}
            ]
            
            # 检查同一文件夹下是否有相同的标题
            if folder_obj_id:
                duplicate_query["folder_id"] = folder_obj_id
            else:
                # 如果没有指定文件夹，检查根目录（folder_id为None或不存在）
                if "$or" in duplicate_query:
                    # 需要组合条件
                    folder_condition = [
                        {"folder_id": None},
                        {"folder_id": {"$exists": False}}
                    ]
                    duplicate_query["$and"] = [
                        {"$or": duplicate_query.pop("$or")},
                        {"$or": folder_condition}
                    ]
                else:
                    duplicate_query["$or"] = [
                        {"folder_id": None},
                        {"folder_id": {"$exists": False}}
                    ]
            
            # 检查标题重复
            title_query = duplicate_query.copy()
            title_query["title"] = file_data.title
            title_duplicate = await self.db.knowledge_files.find_one(title_query)
            if title_duplicate:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail=f'文档已存在：同一文件夹下已存在标题为"{file_data.title}"的文档'
                )
        
        # 构建更新数据
        update_data: Dict[str, Any] = {"updated_at": datetime.utcnow()}
        
        if file_data.title is not None:
            update_data["title"] = file_data.title
        if file_data.description is not None:
            update_data["description"] = file_data.description
        if file_data.tags is not None:
            update_data["tags"] = file_data.tags
        if file_data.permission is not None:
            update_data["permission"] = file_data.permission.value
        if file_data.is_public is not None:
            update_data["is_public"] = file_data.is_public
        
        # 处理文件夹变更
        old_folder_id = file_doc.get("folder_id")
        if file_data.folder_id is not None:
            if file_data.folder_id == "":
                update_data["folder_id"] = None
            else:
                update_data["folder_id"] = self._validate_object_id(file_data.folder_id, "文件夹ID")
        
        # 处理项目变更
        if file_data.project_id is not None:
            if file_data.project_id == "":
                update_data["project_id"] = None
            else:
                update_data["project_id"] = self._validate_object_id(file_data.project_id, "项目ID")
        
        # 更新文件
        await self.db.knowledge_files.update_one(
            {"_id": file_obj_id},
            {"$set": update_data}
        )
        
        # 更新文件夹文件计数
        if old_folder_id != update_data.get("folder_id"):
            if old_folder_id:
                await self.db.knowledge_folders.update_one(
                    {"_id": old_folder_id},
                    {"$inc": {"file_count": -1}}
                )
            if update_data.get("folder_id"):
                await self.db.knowledge_folders.update_one(
                    {"_id": update_data["folder_id"]},
                    {"$inc": {"file_count": 1}}
                )
        
        return await self.get_file(user, file_id)
    
    async def delete_file(self, user: dict, file_id: str) -> None:
        """删除文件（软删除）"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 从状态管理系统动态获取"已删除"状态代码
        deleted_status_code = await self._get_deleted_status_code()
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查文件是否已被删除
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件已被删除"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件已被删除"
            )
        
        # 检查权限（只有创建者可以删除）
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权删除此文件"
            )
        
        # 软删除
        if deleted_status_code:
            await self.db.knowledge_files.update_one(
                {"_id": file_obj_id},
                {
                    "$set": {
                        "status": deleted_status_code,
                        "deleted_at": datetime.utcnow(),
                        "updated_at": datetime.utcnow()
                    }
                }
            )
        else:
            # 如果没有找到"已删除"状态，仍然设置 deleted_at，但不更新 status
            await self.db.knowledge_files.update_one(
                {"_id": file_obj_id},
                {
                    "$set": {
                        "deleted_at": datetime.utcnow(),
                        "updated_at": datetime.utcnow()
                    }
                }
            )
        
        # 更新文件夹文件计数
        if file_doc.get("folder_id"):
            await self.db.knowledge_folders.update_one(
                {"_id": file_doc["folder_id"]},
                {"$inc": {"file_count": -1}}
            )
        
        # 删除MinIO中的文件（可选，也可以保留）
        # try:
        #     minio_client = get_minio()
        #     minio_client.remove_object(self._bucket_name, file_doc.get("file_path", ""))
        # except Exception as e:
        #     logger.warning(f"删除MinIO文件失败: {str(e)}")
    
    async def batch_delete_files(self, user: dict, file_ids: List[str]) -> Dict[str, Any]:
        """批量删除文件（软删除）"""
        success_count = 0
        fail_count = 0
        success_ids = []
        failed_items = []
        
        for file_id in file_ids:
            try:
                await self.delete_file(user=user, file_id=file_id)
                success_count += 1
                success_ids.append(file_id)
            except HTTPException as e:
                fail_count += 1
                failed_items.append({
                    "file_id": file_id,
                    "error": e.detail,
                    "status_code": e.status_code
                })
                logger.warning(f"删除文件 {file_id} 失败: {e.detail}")
            except Exception as e:
                fail_count += 1
                failed_items.append({
                    "file_id": file_id,
                    "error": str(e),
                    "status_code": 500
                })
                logger.error(f"删除文件 {file_id} 时发生异常: {str(e)}", exc_info=True)
        
        return {
            "success_count": success_count,
            "fail_count": fail_count,
            "total_count": len(file_ids),
            "success_ids": success_ids,
            "errors": failed_items
        }
    
    async def download_file(self, user: dict, file_id: str) -> Tuple[bytes, str, str]:
        """下载文件"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        
        # 获取文件信息
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        if file_doc.get("permission") == FilePermission.PRIVATE.value:
            if file_doc.get("author_id") != user_id:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="无权下载此文件"
                )
        
        # 从MinIO下载文件
        try:
            minio_client = get_minio()
            response = minio_client.get_object(self._bucket_name, file_doc.get("file_path", ""))
            file_data = response.read()
            response.close()
            response.release_conn()
        except Exception as e:
            logger.error(f"从MinIO下载文件失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="文件下载失败"
            )
        
        # 更新下载计数
        await self.db.knowledge_files.update_one(
            {"_id": file_obj_id},
            {"$inc": {"download_count": 1}}
        )
        
        return file_data, file_doc.get("mime_type", "application/octet-stream"), file_doc.get("filename", "file")
    
    async def increment_view_count(self, file_id: str) -> None:
        """增加文件查看次数"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        await self.db.knowledge_files.update_one(
            {"_id": file_obj_id},
            {"$inc": {"view_count": 1}}
        )
    
    async def get_file_content(self, user: dict, file_id: str) -> str:
        """获取文件内容（仅支持文本类文件）"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        
        # 获取文件信息
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        if file_doc.get("permission") == FilePermission.PRIVATE.value:
            if file_doc.get("author_id") != user_id:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="无权访问此文件"
                )
        
        # 检查文件格式是否支持编辑
        file_format = file_doc.get("format", "").lower()
        editable_formats = ["txt", "md", "html", "json", "xml", "yaml", "yml", "csv"]
        if file_format not in editable_formats:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"不支持编辑此文件格式: {file_format}"
            )
        
        # 从MinIO读取文件内容
        try:
            minio_client = get_minio()
            response = minio_client.get_object(self._bucket_name, file_doc.get("file_path", ""))
            content = response.read()
            response.close()
            response.release_conn()
            
            # 尝试解码为UTF-8文本
            try:
                text_content = content.decode('utf-8')
            except UnicodeDecodeError:
                # 如果UTF-8解码失败，尝试其他编码
                try:
                    text_content = content.decode('gbk')
                except UnicodeDecodeError:
                    text_content = content.decode('latin-1', errors='ignore')
            
            return text_content
        except Exception as e:
            logger.error(f"读取文件内容失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="读取文件内容失败"
            )
    
    async def save_file_content(self, user: dict, file_id: str, content: str) -> KnowledgeFileResponse:
        """保存文件内容（仅支持文本类文件）"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        
        # 获取文件信息
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        deleted_status_code = await self._get_deleted_status_code()
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限（只有创建者可以编辑）
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权编辑此文件"
            )
        
        # 检查文件格式是否支持编辑
        file_format = file_doc.get("format", "").lower()
        editable_formats = ["txt", "md", "html", "json", "xml", "yaml", "yml", "csv"]
        if file_format not in editable_formats:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"不支持编辑此文件格式: {file_format}"
            )
        
        # 将内容编码为UTF-8字节
        content_bytes = content.encode('utf-8')
        new_file_size = len(content_bytes)
        
        # 上传到MinIO
        try:
            minio_client = get_minio()
            from io import BytesIO
            file_stream = BytesIO(content_bytes)
            
            minio_client.put_object(
                self._bucket_name,
                file_doc.get("file_path", ""),
                file_stream,
                length=new_file_size,
                content_type=file_doc.get("mime_type", "text/plain")
            )
        except Exception as e:
            logger.error(f"保存文件内容失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="保存文件内容失败"
            )
        
        # 更新文件大小和更新时间
        await self.db.knowledge_files.update_one(
            {"_id": file_obj_id},
            {
                "$set": {
                    "file_size": new_file_size,
                    "updated_at": datetime.utcnow()
                }
            }
        )
        
        # 返回更新后的文件信息
        return await self.get_file(user, file_id)
    
    # ========== 文件夹管理方法 ==========
    
    async def create_folder(
        self,
        user: dict,
        folder_data: KnowledgeFolderCreate
    ) -> KnowledgeFolderResponse:
        """创建文件夹"""
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查父文件夹是否存在
        parent_id = None
        if folder_data.parent_id:
            parent_id = self._validate_object_id(folder_data.parent_id, "父文件夹ID")
            parent_folder = await self.db.knowledge_folders.find_one({"_id": parent_id})
            if not parent_folder:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="父文件夹不存在"
                )
            
            # 检查是否会造成循环引用（父文件夹不能是自己的子文件夹）
            # 这里简化处理，只检查一层，实际应该递归检查所有祖先
            # 如果父文件夹的parent_id链中包含当前要创建的文件夹，则拒绝
            # 由于是创建新文件夹，这里只需要检查父文件夹的祖先链即可
            # 但为了简化，我们暂时只检查一层，后续可以优化为递归检查
        
        # 检查同级文件夹名称是否重复
        # 构建查询条件：相同名称、相同父文件夹、相同项目（或都为None）
        query: Dict[str, Any] = {
            "name": folder_data.name,
        }
        
        # 父文件夹ID条件
        if parent_id:
            query["parent_id"] = parent_id
        else:
            # 如果 parent_id 为 None，需要匹配 None 或不存在的情况
            query["$or"] = [
                {"parent_id": None},
                {"parent_id": {"$exists": False}}
            ]
        
        # 项目ID条件
        project_obj_id = self._to_object_id(folder_data.project_id) if folder_data.project_id else None
        if project_obj_id:
            # 如果项目ID不为None，直接添加到查询条件
            if "$or" in query:
                # 已经有 parent_id 的 $or 条件，需要使用 $and 组合
                parent_or = query.pop("$or")
                query["$and"] = [
                    {"$or": parent_or},
                    {"project_id": project_obj_id}
                ]
            else:
                query["project_id"] = project_obj_id
        else:
            # 如果项目ID为None，需要匹配 None 或不存在的情况
            if "$or" in query:
                # 已经有 parent_id 的 $or 条件，需要使用 $and 组合
                parent_or = query.pop("$or")
                query["$and"] = [
                    {"$or": parent_or},
                    {"$or": [
                        {"project_id": None},
                        {"project_id": {"$exists": False}}
                    ]}
                ]
            else:
                query["$or"] = [
                    {"project_id": None},
                    {"project_id": {"$exists": False}}
                ]
        
        existing = await self.db.knowledge_folders.find_one(query)
        if existing:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="同级文件夹中已存在同名文件夹"
            )
        
        # 创建文件夹
        now = datetime.utcnow()
        folder_doc = {
            "name": folder_data.name,
            "parent_id": parent_id,
            "project_id": self._to_object_id(folder_data.project_id) if folder_data.project_id else None,
            "description": folder_data.description,
            "author_id": user_id,
            "organization_id": self._to_object_id(user.get("organization_id")) if user.get("organization_id") else None,
            "permission": folder_data.permission.value,
            "file_count": 0,
            "created_at": now,
            "updated_at": now
        }
        
        result = await self.db.knowledge_folders.insert_one(folder_doc)
        folder_id = result.inserted_id
        
        return await self.get_folder(user, str(folder_id))
    
    async def get_folder(self, user: dict, folder_id: str) -> KnowledgeFolderResponse:
        """获取文件夹详情"""
        folder_obj_id = self._validate_object_id(folder_id, "文件夹ID")
        
        folder_doc = await self.db.knowledge_folders.find_one({"_id": folder_obj_id})
        if not folder_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件夹不存在"
            )
        
        # 获取关联信息
        project_name = None
        if folder_doc.get("project_id"):
            project = await self.db.projects.find_one({"_id": folder_doc["project_id"]})
            if project:
                project_name = project.get("name")
        
        author_name = None
        if folder_doc.get("author_id"):
            author = await self.db.users.find_one({"_id": folder_doc["author_id"]})
            if author:
                author_name = author.get("name") or author.get("username")
        
        return KnowledgeFolderResponse(
            id=str(folder_doc["_id"]),
            name=folder_doc.get("name", ""),
            parent_id=str(folder_doc["parent_id"]) if folder_doc.get("parent_id") else None,
            project_id=str(folder_doc["project_id"]) if folder_doc.get("project_id") else None,
            project_name=project_name,
            description=folder_doc.get("description"),
            author_id=str(folder_doc["author_id"]),
            author_name=author_name,
            organization_id=str(folder_doc["organization_id"]) if folder_doc.get("organization_id") else None,
            permission=folder_doc.get("permission", FolderPermission.PRIVATE.value),
            file_count=folder_doc.get("file_count", 0),
            created_at=folder_doc.get("created_at", datetime.utcnow()),
            updated_at=folder_doc.get("updated_at", datetime.utcnow()),
            children=None
        )
    
    async def get_folders(
        self,
        user: dict,
        project_id: Optional[str] = None,
        tree: bool = False
    ) -> List[KnowledgeFolderResponse]:
        """获取文件夹列表"""
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 构建查询条件
        query: Dict[str, Any] = {}
        
        project_conditions = []
        if project_id:
            query["project_id"] = self._validate_object_id(project_id, "项目ID")
        else:
            # 如果没有指定项目，显示所有文件夹（包括未关联项目的）
            project_conditions = [
                {"project_id": None},
                {"project_id": {"$exists": False}}
            ]
        
        # 权限筛选（简化版）
        permission_conditions = [
            {"permission": FolderPermission.PUBLIC.value},
            {"permission": FolderPermission.TEAM.value},
            {"author_id": user_id}
        ]
        
        # 组合项目条件和权限条件
        if project_conditions:
            query["$and"] = [
                {"$or": project_conditions},
                {"$or": permission_conditions}
            ]
        else:
            query["$or"] = permission_conditions
        
        # 查询文件夹
        cursor = self.db.knowledge_folders.find(query).sort("created_at", ASCENDING)
        all_folders = []
        folder_map: Dict[str, KnowledgeFolderResponse] = {}
        
        # 第一遍：读取所有文件夹并创建映射
        async for folder_doc in cursor:
            # 获取关联信息
            project_name = None
            if folder_doc.get("project_id"):
                project = await self.db.projects.find_one({"_id": folder_doc["project_id"]})
                if project:
                    project_name = project.get("name")
            
            author_name = None
            if folder_doc.get("author_id"):
                author = await self.db.users.find_one({"_id": folder_doc["author_id"]})
                if author:
                    author_name = author.get("name") or author.get("username")
            
            # 重新计算该文件夹的直接文件数量（不包括子文件夹）
            folder_id_obj = folder_doc["_id"]
            # 从状态管理系统动态获取"已删除"状态代码并过滤
            deleted_status_code = await self._get_deleted_status_code()
            # 构建文件查询条件：只统计当前文件夹的直接文件，排除已删除的文件
            file_count_conditions = []
            
            # 1. 过滤deleted_at字段（无论是否有已删除状态码，都要过滤deleted_at）
            file_count_conditions.append({
                "$or": [
                    {"deleted_at": None},
                    {"deleted_at": {"$exists": False}}
                ]
            })
            
            # 2. 文件夹筛选：只统计直接属于当前文件夹的文件（不包括子文件夹）
            file_count_conditions.append({"folder_id": folder_id_obj})
            
            # 3. 过滤已删除状态（如果有已删除状态码）
            if deleted_status_code:
                file_count_conditions.append({"status": {"$ne": deleted_status_code}})
            
            # 4. 添加权限筛选（只统计用户可见的文件）
            file_permission_conditions = [
                {"is_public": True},
                {"permission": FilePermission.PUBLIC.value},
                {"permission": FilePermission.TEAM.value},
                {"author_id": user_id}
            ]
            file_count_conditions.append({"$or": file_permission_conditions})
            
            # 组合所有条件
            file_count_query = {"$and": file_count_conditions} if len(file_count_conditions) > 0 else {}
            
            # 统计该文件夹的直接文件数量（不包括子文件夹）
            actual_file_count = await self.db.knowledge_files.count_documents(file_count_query)
            
            folder_resp = KnowledgeFolderResponse(
                id=str(folder_doc["_id"]),
                name=folder_doc.get("name", ""),
                parent_id=str(folder_doc["parent_id"]) if folder_doc.get("parent_id") else None,
                project_id=str(folder_doc["project_id"]) if folder_doc.get("project_id") else None,
                project_name=project_name,
                description=folder_doc.get("description"),
                author_id=str(folder_doc["author_id"]),
                author_name=author_name,
                organization_id=str(folder_doc["organization_id"]) if folder_doc.get("organization_id") else None,
                permission=folder_doc.get("permission", FolderPermission.PRIVATE.value),
                file_count=actual_file_count,  # 使用实际计算的文件数量
                created_at=folder_doc.get("created_at", datetime.utcnow()),
                updated_at=folder_doc.get("updated_at", datetime.utcnow()),
                children=[]
            )
            
            folder_map[str(folder_doc["_id"])] = folder_resp
            all_folders.append(folder_resp)
        
        if tree:
            # 第二遍：构建树形结构
            root_folders = []
            for folder in all_folders:
                if folder.parent_id and folder.parent_id in folder_map:
                    # 有父节点且父节点存在，添加到父节点的children
                    parent = folder_map[folder.parent_id]
                    if parent.children is None:
                        parent.children = []
                    parent.children.append(folder)
                else:
                    # 根节点或父节点不存在
                    root_folders.append(folder)
            
            # 第三遍：递归计算每个文件夹的总文件数（包括所有子文件夹）
            # 注意：需要从叶子节点开始向上累加，确保父文件夹包含所有子文件夹的文件
            def calculate_total_file_count_recursive(folder: KnowledgeFolderResponse) -> int:
                """
                递归计算文件夹及其所有子文件夹的文件总数
                返回：文件夹本身的直接文件数 + 所有子文件夹（包括子文件夹的子文件夹）的文件总数
                """
                # 先保存文件夹的直接文件数（在计算子文件夹之前）
                direct_file_count = folder.file_count or 0
                
                # 递归计算所有子文件夹的文件总数
                # 这里会递归处理所有子文件夹，包括子文件夹的子文件夹
                children_total_count = 0
                if folder.children and len(folder.children) > 0:
                    for child_folder in folder.children:
                        # 递归计算子文件夹的总文件数（包括它的子文件夹）
                        child_total = calculate_total_file_count_recursive(child_folder)
                        children_total_count += child_total
                
                # 更新文件夹的文件数为：直接文件数 + 所有子文件夹的文件总数
                total_file_count = direct_file_count + children_total_count
                folder.file_count = total_file_count
                
                return total_file_count
            
            # 从根文件夹开始，递归计算并更新所有文件夹的文件数量
            # 这会从叶子节点开始向上累加，确保每一层都正确统计
            for root_folder in root_folders:
                calculate_total_file_count_recursive(root_folder)
            
            return root_folders
        else:
            return all_folders
    
    async def update_folder(
        self,
        user: dict,
        folder_id: str,
        folder_data: KnowledgeFolderUpdate
    ) -> KnowledgeFolderResponse:
        """更新文件夹"""
        folder_obj_id = self._validate_object_id(folder_id, "文件夹ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件夹是否存在
        folder_doc = await self.db.knowledge_folders.find_one({"_id": folder_obj_id})
        if not folder_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件夹不存在"
            )
        
        # 检查权限
        if folder_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权修改此文件夹"
            )
        
        # 构建更新数据
        update_data: Dict[str, Any] = {"updated_at": datetime.utcnow()}
        
        if folder_data.name is not None:
            update_data["name"] = folder_data.name
        if folder_data.description is not None:
            update_data["description"] = folder_data.description
        if folder_data.permission is not None:
            update_data["permission"] = folder_data.permission.value
        if folder_data.parent_id is not None:
            if folder_data.parent_id == "":
                update_data["parent_id"] = None
            else:
                new_parent_id = self._validate_object_id(folder_data.parent_id, "父文件夹ID")
                # 检查是否会造成循环引用
                if new_parent_id == folder_obj_id:
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="不能将文件夹设置为自己的父文件夹"
                    )
                
                # 检查新父文件夹是否存在
                parent_folder = await self.db.knowledge_folders.find_one({"_id": new_parent_id})
                if not parent_folder:
                    raise HTTPException(
                        status_code=status.HTTP_404_NOT_FOUND,
                        detail="父文件夹不存在"
                    )
                
                # 检查深层循环引用：不能将文件夹移动到其子文件夹下
                async def is_descendant(ancestor_id: ObjectId, descendant_id: ObjectId) -> bool:
                    """检查descendant是否是ancestor的后代"""
                    children = await self.db.knowledge_folders.find({"parent_id": ancestor_id}).to_list(length=None)
                    for child in children:
                        if child["_id"] == descendant_id:
                            return True
                        if await is_descendant(child["_id"], descendant_id):
                            return True
                    return False
                
                if await is_descendant(folder_obj_id, new_parent_id):
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="不能将文件夹移动到其子文件夹下"
                    )
                
                update_data["parent_id"] = new_parent_id
        
        # 更新文件夹
        await self.db.knowledge_folders.update_one(
            {"_id": folder_obj_id},
            {"$set": update_data}
        )
        
        return await self.get_folder(user, folder_id)
    
    async def delete_folder(self, user: dict, folder_id: str) -> None:
        """删除文件夹"""
        folder_obj_id = self._validate_object_id(folder_id, "文件夹ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件夹是否存在
        folder_doc = await self.db.knowledge_folders.find_one({"_id": folder_obj_id})
        if not folder_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件夹不存在"
            )
        
        # 检查权限
        if folder_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权删除此文件夹"
            )
        
        # 检查是否有子文件夹
        child_count = await self.db.knowledge_folders.count_documents({"parent_id": folder_obj_id})
        if child_count > 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件夹下存在子文件夹，无法删除"
            )
        
        # 检查是否有文件
        # 从状态管理系统动态获取"已删除"状态代码并过滤
        deleted_status_code = await self._get_deleted_status_code()
        file_count_query = {
            "folder_id": folder_obj_id
        }
        if deleted_status_code:
            file_count_query["status"] = {"$ne": deleted_status_code}
        file_count = await self.db.knowledge_files.count_documents(file_count_query)
        if file_count > 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件夹下存在文件，无法删除"
            )
        
        # 删除文件夹
        await self.db.knowledge_folders.delete_one({"_id": folder_obj_id})
    
    # ========== 分享功能方法 ==========
    
    async def create_share(
        self,
        user: dict,
        file_id: str,
        share_data: KnowledgeShareCreate
    ) -> KnowledgeShareResponse:
        """创建文件分享链接"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限（只有创建者可以分享）
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权分享此文件"
            )
        
        # 检查是否已有分享链接
        existing_share = await self.db.knowledge_shares.find_one({
            "file_id": file_obj_id,
            "revoked_at": None
        })
        
        if existing_share:
            # 如果已有分享链接，更新它
            token = existing_share.get("token")
            share_id = existing_share["_id"]
        else:
            # 生成新的分享令牌
            token = secrets.token_urlsafe(32)
            share_id = ObjectId()
        
        # 计算过期时间
        expires_at = None
        if share_data.expires_in_hours:
            expires_at = datetime.utcnow() + timedelta(hours=share_data.expires_in_hours)
        
        # 构建分享URL
        from app.core.config import settings
        base_url = getattr(settings, "FRONTEND_URL", "http://localhost:3000")
        share_url = f"{base_url}/share/knowledge/{token}"
        
        # 保存或更新分享记录
        share_doc = {
            "_id": share_id,
            "file_id": file_obj_id,
            "token": token,
            "share_url": share_url,
            "allow_download": share_data.allow_download,
            "expires_at": expires_at,
            "access_count": existing_share.get("access_count", 0) if existing_share else 0,
            "max_access_count": share_data.max_access_count,
            "password": share_data.password,
            "created_by": user_id,
            "created_at": existing_share.get("created_at", datetime.utcnow()) if existing_share else datetime.utcnow(),
            "revoked_at": None
        }
        
        if existing_share:
            await self.db.knowledge_shares.update_one(
                {"_id": share_id},
                {"$set": {
                    "allow_download": share_data.allow_download,
                    "expires_at": expires_at,
                    "max_access_count": share_data.max_access_count,
                    "password": share_data.password
                }}
            )
        else:
            await self.db.knowledge_shares.insert_one(share_doc)
        
        return KnowledgeShareResponse(
            id=str(share_id),
            file_id=str(file_obj_id),
            token=token,
            share_url=share_url,
            allow_download=share_data.allow_download,
            expires_at=expires_at,
            access_count=share_doc["access_count"],
            max_access_count=share_data.max_access_count,
            has_password=bool(share_data.password),
            created_by=str(user_id),
            created_at=share_doc["created_at"],
            is_active=True
        )
    
    async def get_share(self, user: dict, file_id: str) -> Optional[KnowledgeShareResponse]:
        """获取文件分享信息"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权查看此文件的分享信息"
            )
        
        # 查找分享记录
        share_doc = await self.db.knowledge_shares.find_one({
            "file_id": file_obj_id,
            "revoked_at": None
        })
        
        if not share_doc:
            return None
        
        # 检查是否过期
        is_active = True
        if share_doc.get("expires_at"):
            if datetime.utcnow() > share_doc["expires_at"]:
                is_active = False
        
        # 检查访问次数限制
        if share_doc.get("max_access_count"):
            if share_doc.get("access_count", 0) >= share_doc["max_access_count"]:
                is_active = False
        
        return KnowledgeShareResponse(
            id=str(share_doc["_id"]),
            file_id=str(file_obj_id),
            token=share_doc.get("token", ""),
            share_url=share_doc.get("share_url", ""),
            allow_download=share_doc.get("allow_download", True),
            expires_at=share_doc.get("expires_at"),
            access_count=share_doc.get("access_count", 0),
            max_access_count=share_doc.get("max_access_count"),
            has_password=bool(share_doc.get("password")),
            created_by=str(share_doc["created_by"]),
            created_at=share_doc.get("created_at", datetime.utcnow()),
            is_active=is_active
        )
    
    async def revoke_share(self, user: dict, file_id: str) -> None:
        """撤销文件分享链接"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查权限
        if file_doc.get("author_id") != user_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="无权撤销此文件的分享链接"
            )
        
        # 撤销分享
        await self.db.knowledge_shares.update_one(
            {"file_id": file_obj_id, "revoked_at": None},
            {"$set": {"revoked_at": datetime.utcnow()}}
        )
    
    async def get_file_by_share_token(self, token: str, password: Optional[str] = None) -> Tuple[KnowledgeFileResponse, KnowledgeShareResponse]:
        """通过分享令牌获取文件"""
        # 查找分享记录
        share_doc = await self.db.knowledge_shares.find_one({
            "token": token,
            "revoked_at": None
        })
        
        if not share_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="分享链接不存在或已失效"
            )
        
        # 检查密码
        if share_doc.get("password"):
            if not password or share_doc["password"] != password:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="分享链接密码错误"
                )
        
        # 检查是否过期
        if share_doc.get("expires_at"):
            if datetime.utcnow() > share_doc["expires_at"]:
                raise HTTPException(
                    status_code=status.HTTP_410_GONE,
                    detail="分享链接已过期"
                )
        
        # 检查访问次数限制
        if share_doc.get("max_access_count"):
            if share_doc.get("access_count", 0) >= share_doc["max_access_count"]:
                raise HTTPException(
                    status_code=status.HTTP_410_GONE,
                    detail="分享链接访问次数已达上限"
                )
        
        # 增加访问次数
        await self.db.knowledge_shares.update_one(
            {"_id": share_doc["_id"]},
            {
                "$inc": {"access_count": 1},
                "$set": {"last_accessed_at": datetime.utcnow()}
            }
        )
        
        # 获取文件信息
        file_doc = await self.db.knowledge_files.find_one({"_id": share_doc["file_id"]})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 构建文件响应
        file_resp = KnowledgeFileResponse(
            id=str(file_doc["_id"]),
            title=file_doc.get("title", ""),
            filename=file_doc.get("filename", ""),
            file_path=file_doc.get("file_path", ""),
            file_size=file_doc.get("file_size", 0),
            mime_type=file_doc.get("mime_type", ""),
            file_type=file_doc.get("file_type", ""),
            format=file_doc.get("format", ""),
            folder_id=None,
            folder_name=None,
            tags=file_doc.get("tags", []),
            description=file_doc.get("description"),
            project_id=None,
            project_name=None,
            author_id=str(file_doc["author_id"]),
            author_name=None,
            organization_id=None,
            is_public=file_doc.get("is_public", False),
            permission=file_doc.get("permission", FilePermission.PRIVATE.value),
            download_count=file_doc.get("download_count", 0),
            view_count=file_doc.get("view_count", 0),
            favorite_count=file_doc.get("favorite_count", 0),
            status=file_doc.get("status", ""),
            created_at=file_doc.get("created_at", datetime.utcnow()),
            updated_at=file_doc.get("updated_at", datetime.utcnow()),
            is_favorited=False
        )
        
        # 构建分享响应
        share_resp = KnowledgeShareResponse(
            id=str(share_doc["_id"]),
            file_id=str(share_doc["file_id"]),
            token=share_doc.get("token", ""),
            share_url=share_doc.get("share_url", ""),
            allow_download=share_doc.get("allow_download", True),
            expires_at=share_doc.get("expires_at"),
            access_count=share_doc.get("access_count", 0) + 1,
            max_access_count=share_doc.get("max_access_count"),
            has_password=bool(share_doc.get("password")),
            created_by=str(share_doc["created_by"]),
            created_at=share_doc.get("created_at", datetime.utcnow()),
            is_active=True
        )
        
        return file_resp, share_resp
    
    async def download_file_by_share_token(self, token: str, password: Optional[str] = None) -> Tuple[bytes, str, str]:
        """通过分享令牌下载文件"""
        # 查找分享记录
        share_doc = await self.db.knowledge_shares.find_one({
            "token": token,
            "revoked_at": None
        })
        
        if not share_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="分享链接不存在或已失效"
            )
        
        # 检查密码
        if share_doc.get("password"):
            if not password or share_doc["password"] != password:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="分享链接密码错误"
                )
        
        # 检查是否过期
        if share_doc.get("expires_at"):
            if datetime.utcnow() > share_doc["expires_at"]:
                raise HTTPException(
                    status_code=status.HTTP_410_GONE,
                    detail="分享链接已过期"
                )
        
        # 检查访问次数限制
        if share_doc.get("max_access_count"):
            if share_doc.get("access_count", 0) >= share_doc["max_access_count"]:
                raise HTTPException(
                    status_code=status.HTTP_410_GONE,
                    detail="分享链接访问次数已达上限"
                )
        
        # 检查是否允许下载
        if not share_doc.get("allow_download", True):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="此分享链接不允许下载"
            )
        
        # 获取文件信息
        file_doc = await self.db.knowledge_files.find_one({"_id": share_doc["file_id"]})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 从MinIO读取文件
        try:
            minio_client = get_minio()
            response = minio_client.get_object(self._bucket_name, file_doc.get("file_path", ""))
            file_data = response.read()
            response.close()
            response.release_conn()
            
            mime_type = file_doc.get("mime_type", "application/octet-stream")
            filename = file_doc.get("filename", "file")
            
            return file_data, mime_type, filename
        except Exception as e:
            logger.error(f"下载文件失败: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="下载文件失败"
            )
    
    # ========== 评论功能方法 ==========
    
    async def create_comment(
        self,
        user: dict,
        file_id: str,
        comment_data: KnowledgeCommentCreate
    ) -> KnowledgeCommentResponse:
        """创建文件评论"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查父评论是否存在
        parent_id = None
        if comment_data.parent_id:
            parent_id = self._validate_object_id(comment_data.parent_id, "父评论ID")
            parent_comment = await self.db.knowledge_comments.find_one({"_id": parent_id})
            if not parent_comment:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="父评论不存在"
                )
        
        # 创建评论
        now = datetime.utcnow()
        comment_doc = {
            "file_id": file_obj_id,
            "user_id": user_id,
            "content": comment_data.content,
            "parent_id": parent_id,
            "created_at": now,
            "updated_at": now
        }
        
        result = await self.db.knowledge_comments.insert_one(comment_doc)
        comment_id = result.inserted_id
        
        # 获取用户信息
        user_doc = await self.db.users.find_one({"_id": user_id})
        user_name = ""
        user_avatar = None
        if user_doc:
            user_name = user_doc.get("name") or user_doc.get("username", "")
            user_avatar = user_doc.get("avatar")
        else:
            # 如果数据库中没有找到，使用传入的user字典
            user_name = user.get("name") or user.get("username", "")
            user_avatar = user.get("avatar")
        
        return KnowledgeCommentResponse(
            id=str(comment_id),
            file_id=str(file_obj_id),
            user_id=str(user_id),
            user_name=user_name,
            user_avatar=user_avatar,
            content=comment_data.content,
            parent_id=str(parent_id) if parent_id else None,
            created_at=now,
            updated_at=now
        )
    
    async def get_comments(self, file_id: str) -> List[KnowledgeCommentResponse]:
        """获取文件评论列表"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        
        # 查询评论
        cursor = self.db.knowledge_comments.find({"file_id": file_obj_id}).sort("created_at", ASCENDING)
        comments = []
        
        async for comment_doc in cursor:
            # 获取用户信息
            user_doc = await self.db.users.find_one({"_id": comment_doc["user_id"]})
            user_name = ""
            user_avatar = None
            if user_doc:
                user_name = user_doc.get("name") or user_doc.get("username", "")
                user_avatar = user_doc.get("avatar")
            
            comments.append(KnowledgeCommentResponse(
                id=str(comment_doc["_id"]),
                file_id=str(file_obj_id),
                user_id=str(comment_doc["user_id"]),
                user_name=user_name,
                user_avatar=user_avatar,
                content=comment_doc.get("content", ""),
                parent_id=str(comment_doc["parent_id"]) if comment_doc.get("parent_id") else None,
                created_at=comment_doc.get("created_at", datetime.utcnow()),
                updated_at=comment_doc.get("updated_at", datetime.utcnow())
            ))
        
        return comments
    
    # ========== 收藏功能方法 ==========
    
    async def favorite_file(self, user: dict, file_id: str) -> None:
        """收藏文件"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 检查文件是否存在
        file_doc = await self.db.knowledge_files.find_one({"_id": file_obj_id})
        deleted_status_code = await self._get_deleted_status_code()
        if not file_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        # 检查文件是否已删除（检查status和deleted_at）
        if deleted_status_code and file_doc.get("status") == deleted_status_code:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        if file_doc.get("deleted_at"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="文件不存在"
            )
        
        # 检查是否已收藏
        existing = await self.db.knowledge_favorites.find_one({
            "file_id": file_obj_id,
            "user_id": user_id
        })
        
        if existing:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="文件已收藏"
            )
        
        # 添加收藏
        await self.db.knowledge_favorites.insert_one({
            "file_id": file_obj_id,
            "user_id": user_id,
            "created_at": datetime.utcnow()
        })
        
        # 更新文件收藏数
        await self.db.knowledge_files.update_one(
            {"_id": file_obj_id},
            {"$inc": {"favorite_count": 1}}
        )
    
    async def unfavorite_file(self, user: dict, file_id: str) -> None:
        """取消收藏文件"""
        file_obj_id = self._validate_object_id(file_id, "文件ID")
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 删除收藏
        result = await self.db.knowledge_favorites.delete_one({
            "file_id": file_obj_id,
            "user_id": user_id
        })
        
        if result.deleted_count > 0:
            # 更新文件收藏数
            await self.db.knowledge_files.update_one(
                {"_id": file_obj_id},
                {"$inc": {"favorite_count": -1}}
            )
    
    async def get_favorite_files(
        self,
        user: dict,
        page: int = 1,
        size: int = 20
    ) -> Tuple[List[KnowledgeFileResponse], int]:
        """获取收藏的文件列表"""
        user_id = ObjectId(user.get("user_id") or user.get("id"))
        
        # 查询收藏记录
        skip = (page - 1) * size
        cursor = self.db.knowledge_favorites.find({"user_id": user_id}).sort("created_at", DESCENDING).skip(skip).limit(size)
        
        file_ids = []
        async for favorite_doc in cursor:
            file_ids.append(favorite_doc["file_id"])
        
        # 查询文件
        files = []
        for file_id in file_ids:
            try:
                file = await self.get_file(user, str(file_id))
                files.append(file)
            except Exception:
                continue
        
        # 计算总数
        total = await self.db.knowledge_favorites.count_documents({"user_id": user_id})
        
        return files, total

